{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "whjsJasuhstV"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/jeffheaton/app_generative_ai/blob/main/t81_559_class_07_2_tools.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "euOZxlIMhstX"
   },
   "source": [
    "# T81-559: Applications of Generative Artificial Intelligence\n",
    "**Module 7: LangChain: Agents**\n",
    "* Instructor: [Jeff Heaton](https://sites.wustl.edu/jeffheaton/), McKelvey School of Engineering, [Washington University in St. Louis](https://engineering.wustl.edu/Programs/Pages/default.aspx)\n",
    "* For more information visit the [class website](https://sites.wustl.edu/jeffheaton/t81-558/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "d4Yov72PhstY"
   },
   "source": [
    "# Module 7 Material\n",
    "\n",
    "* Part 7.1: Introduction to LangChain Agents [[Video]](https://www.youtube.com/watch?v=J5Vr___lSSs) [[Notebook]](t81_559_class_07_1_agents.ipynb)\n",
    "* **Part 7.2: Understanding LangChain Agent Tools** [[Video]](https://www.youtube.com/watch?v=qMquBmteYw4) [[Notebook]](t81_559_class_07_2_tools.ipynb)\n",
    "* Part 7.3: LangChain Retrival and Search Tools [[Video]](https://www.youtube.com/watch?v=NB5qGPLoBBE) [[Notebook]](t81_559_class_07_3_search_tools.ipynb)\n",
    "* Part 7.4: Constructing LangChain Agents [[Video]](https://www.youtube.com/watch?v=OJe5oHvrdHk) [[Notebook]](t81_559_class_07_4_more_agent.ipynb)\n",
    "* Part 7.5: Custom Agents [[Video]](https://www.youtube.com/watch?v=IsJemVYSEdc) [[Notebook]](t81_559_class_07_5_custom_agent.ipynb)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "AcAUP0c3hstY"
   },
   "source": [
    "# Google CoLab Instructions\n",
    "\n",
    "The following code ensures that Google CoLab is running and maps Google Drive if needed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "xsI496h5hstZ",
    "outputId": "49979b55-7f83-4c13-ebfc-fc957c569c3a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: using Google CoLab\n",
      "Collecting langchain\n",
      "  Downloading langchain-0.2.16-py3-none-any.whl.metadata (7.1 kB)\n",
      "Collecting langchain_openai\n",
      "  Downloading langchain_openai-0.1.23-py3-none-any.whl.metadata (2.6 kB)\n",
      "Collecting langchain_experimental\n",
      "  Downloading langchain_experimental-0.0.65-py3-none-any.whl.metadata (1.7 kB)\n",
      "Collecting duckduckgo-search\n",
      "  Downloading duckduckgo_search-6.2.11-py3-none-any.whl.metadata (24 kB)\n",
      "Collecting langchainhub\n",
      "  Downloading langchainhub-0.1.21-py3-none-any.whl.metadata (659 bytes)\n",
      "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (6.0.2)\n",
      "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.0.32)\n",
      "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (3.10.5)\n",
      "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (4.0.3)\n",
      "Collecting langchain-core<0.3.0,>=0.2.38 (from langchain)\n",
      "  Downloading langchain_core-0.2.38-py3-none-any.whl.metadata (6.2 kB)\n",
      "Collecting langchain-text-splitters<0.3.0,>=0.2.0 (from langchain)\n",
      "  Downloading langchain_text_splitters-0.2.4-py3-none-any.whl.metadata (2.3 kB)\n",
      "Collecting langsmith<0.2.0,>=0.1.17 (from langchain)\n",
      "  Downloading langsmith-0.1.116-py3-none-any.whl.metadata (13 kB)\n",
      "Requirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.26.4)\n",
      "Requirement already satisfied: pydantic<3,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.8.2)\n",
      "Requirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.32.3)\n",
      "Collecting tenacity!=8.4.0,<9.0.0,>=8.1.0 (from langchain)\n",
      "  Downloading tenacity-8.5.0-py3-none-any.whl.metadata (1.2 kB)\n",
      "Collecting openai<2.0.0,>=1.40.0 (from langchain_openai)\n",
      "  Downloading openai-1.44.0-py3-none-any.whl.metadata (22 kB)\n",
      "Collecting tiktoken<1,>=0.7 (from langchain_openai)\n",
      "  Downloading tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)\n",
      "Collecting langchain-community<0.3.0,>=0.2.16 (from langchain_experimental)\n",
      "  Downloading langchain_community-0.2.16-py3-none-any.whl.metadata (2.7 kB)\n",
      "Requirement already satisfied: click>=8.1.7 in /usr/local/lib/python3.10/dist-packages (from duckduckgo-search) (8.1.7)\n",
      "Collecting primp>=0.6.1 (from duckduckgo-search)\n",
      "  Downloading primp-0.6.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (10 kB)\n",
      "Requirement already satisfied: packaging<25,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchainhub) (24.1)\n",
      "Collecting types-requests<3.0.0.0,>=2.31.0.2 (from langchainhub)\n",
      "  Downloading types_requests-2.32.0.20240907-py3-none-any.whl.metadata (1.9 kB)\n",
      "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (2.4.0)\n",
      "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n",
      "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (24.2.0)\n",
      "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.4.1)\n",
      "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.0.5)\n",
      "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.9.4)\n",
      "Collecting dataclasses-json<0.7,>=0.5.7 (from langchain-community<0.3.0,>=0.2.16->langchain_experimental)\n",
      "  Downloading dataclasses_json-0.6.7-py3-none-any.whl.metadata (25 kB)\n",
      "Collecting jsonpatch<2.0,>=1.33 (from langchain-core<0.3.0,>=0.2.38->langchain)\n",
      "  Downloading jsonpatch-1.33-py2.py3-none-any.whl.metadata (3.0 kB)\n",
      "Requirement already satisfied: typing-extensions>=4.7 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.3.0,>=0.2.38->langchain) (4.12.2)\n",
      "Collecting httpx<1,>=0.23.0 (from langsmith<0.2.0,>=0.1.17->langchain)\n",
      "  Downloading httpx-0.27.2-py3-none-any.whl.metadata (7.1 kB)\n",
      "Collecting orjson<4.0.0,>=3.9.14 (from langsmith<0.2.0,>=0.1.17->langchain)\n",
      "  Downloading orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (50 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m50.4/50.4 kB\u001b[0m \u001b[31m1.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.40.0->langchain_openai) (3.7.1)\n",
      "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai<2.0.0,>=1.40.0->langchain_openai) (1.7.0)\n",
      "Collecting jiter<1,>=0.4.0 (from openai<2.0.0,>=1.40.0->langchain_openai)\n",
      "  Downloading jiter-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.6 kB)\n",
      "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.40.0->langchain_openai) (1.3.1)\n",
      "Requirement already satisfied: tqdm>4 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.40.0->langchain_openai) (4.66.5)\n",
      "Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (0.7.0)\n",
      "Requirement already satisfied: pydantic-core==2.20.1 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (2.20.1)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.3.2)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.8)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2.0.7)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2024.8.30)\n",
      "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.0.3)\n",
      "Requirement already satisfied: regex>=2022.1.18 in /usr/local/lib/python3.10/dist-packages (from tiktoken<1,>=0.7->langchain_openai) (2024.5.15)\n",
      "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai<2.0.0,>=1.40.0->langchain_openai) (1.2.2)\n",
      "Collecting marshmallow<4.0.0,>=3.18.0 (from dataclasses-json<0.7,>=0.5.7->langchain-community<0.3.0,>=0.2.16->langchain_experimental)\n",
      "  Downloading marshmallow-3.22.0-py3-none-any.whl.metadata (7.2 kB)\n",
      "Collecting typing-inspect<1,>=0.4.0 (from dataclasses-json<0.7,>=0.5.7->langchain-community<0.3.0,>=0.2.16->langchain_experimental)\n",
      "  Downloading typing_inspect-0.9.0-py3-none-any.whl.metadata (1.5 kB)\n",
      "Collecting httpcore==1.* (from httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.17->langchain)\n",
      "  Downloading httpcore-1.0.5-py3-none-any.whl.metadata (20 kB)\n",
      "Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.17->langchain)\n",
      "  Downloading h11-0.14.0-py3-none-any.whl.metadata (8.2 kB)\n",
      "Collecting jsonpointer>=1.9 (from jsonpatch<2.0,>=1.33->langchain-core<0.3.0,>=0.2.38->langchain)\n",
      "  Downloading jsonpointer-3.0.0-py2.py3-none-any.whl.metadata (2.3 kB)\n",
      "Collecting mypy-extensions>=0.3.0 (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain-community<0.3.0,>=0.2.16->langchain_experimental)\n",
      "  Downloading mypy_extensions-1.0.0-py3-none-any.whl.metadata (1.1 kB)\n",
      "Downloading langchain-0.2.16-py3-none-any.whl (1.0 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.0/1.0 MB\u001b[0m \u001b[31m25.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading langchain_openai-0.1.23-py3-none-any.whl (51 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m52.0/52.0 kB\u001b[0m \u001b[31m3.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading langchain_experimental-0.0.65-py3-none-any.whl (207 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m207.2/207.2 kB\u001b[0m \u001b[31m11.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading duckduckgo_search-6.2.11-py3-none-any.whl (27 kB)\n",
      "Downloading langchainhub-0.1.21-py3-none-any.whl (5.2 kB)\n",
      "Downloading langchain_community-0.2.16-py3-none-any.whl (2.3 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.3/2.3 MB\u001b[0m \u001b[31m39.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading langchain_core-0.2.38-py3-none-any.whl (396 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m396.4/396.4 kB\u001b[0m \u001b[31m19.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading langchain_text_splitters-0.2.4-py3-none-any.whl (25 kB)\n",
      "Downloading langsmith-0.1.116-py3-none-any.whl (290 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m290.4/290.4 kB\u001b[0m \u001b[31m11.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading openai-1.44.0-py3-none-any.whl (367 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m367.8/367.8 kB\u001b[0m \u001b[31m15.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading primp-0.6.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.8 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.8/2.8 MB\u001b[0m \u001b[31m49.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading tenacity-8.5.0-py3-none-any.whl (28 kB)\n",
      "Downloading tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m37.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading types_requests-2.32.0.20240907-py3-none-any.whl (15 kB)\n",
      "Downloading dataclasses_json-0.6.7-py3-none-any.whl (28 kB)\n",
      "Downloading httpx-0.27.2-py3-none-any.whl (76 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m76.4/76.4 kB\u001b[0m \u001b[31m5.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading httpcore-1.0.5-py3-none-any.whl (77 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m77.9/77.9 kB\u001b[0m \u001b[31m4.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading jiter-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (318 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m318.9/318.9 kB\u001b[0m \u001b[31m18.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading jsonpatch-1.33-py2.py3-none-any.whl (12 kB)\n",
      "Downloading orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (141 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m141.9/141.9 kB\u001b[0m \u001b[31m9.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading jsonpointer-3.0.0-py2.py3-none-any.whl (7.6 kB)\n",
      "Downloading marshmallow-3.22.0-py3-none-any.whl (49 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.3/49.3 kB\u001b[0m \u001b[31m3.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)\n",
      "Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m3.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hDownloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)\n",
      "Installing collected packages: types-requests, tenacity, primp, orjson, mypy-extensions, marshmallow, jsonpointer, jiter, h11, typing-inspect, tiktoken, langchainhub, jsonpatch, httpcore, duckduckgo-search, httpx, dataclasses-json, openai, langsmith, langchain-core, langchain-text-splitters, langchain_openai, langchain, langchain-community, langchain_experimental\n",
      "  Attempting uninstall: tenacity\n",
      "    Found existing installation: tenacity 9.0.0\n",
      "    Uninstalling tenacity-9.0.0:\n",
      "      Successfully uninstalled tenacity-9.0.0\n",
      "Successfully installed dataclasses-json-0.6.7 duckduckgo-search-6.2.11 h11-0.14.0 httpcore-1.0.5 httpx-0.27.2 jiter-0.5.0 jsonpatch-1.33 jsonpointer-3.0.0 langchain-0.2.16 langchain-community-0.2.16 langchain-core-0.2.38 langchain-text-splitters-0.2.4 langchain_experimental-0.0.65 langchain_openai-0.1.23 langchainhub-0.1.21 langsmith-0.1.116 marshmallow-3.22.0 mypy-extensions-1.0.0 openai-1.44.0 orjson-3.10.7 primp-0.6.1 tenacity-8.5.0 tiktoken-0.7.0 types-requests-2.32.0.20240907 typing-inspect-0.9.0\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "\n",
    "try:\n",
    "    from google.colab import drive, userdata\n",
    "    COLAB = True\n",
    "    print(\"Note: using Google CoLab\")\n",
    "except:\n",
    "    print(\"Note: not using Google CoLab\")\n",
    "    COLAB = False\n",
    "\n",
    "# OpenAI Secrets\n",
    "if COLAB:\n",
    "    os.environ[\"OPENAI_API_KEY\"] = userdata.get('OPENAI_API_KEY')\n",
    "\n",
    "# Install needed libraries in CoLab\n",
    "if COLAB:\n",
    "    !pip install langchain langchain_openai langchain_experimental duckduckgo-search langchainhub"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "pC9A-LaYhsta"
   },
   "source": [
    "# 7.2: LangChain Agent Tools\n",
    "\n",
    "\n",
    "LangChain agents are versatile entities designed to perform specific tasks autonomously. Central to their functionality are [tools](https://python.langchain.com/v0.1/docs/modules/tools/), which are specialized components that agents can utilize to accomplish their objectives. These tools can range from data retrieval and processing utilities to interactive interfaces for user engagement. By leveraging these tools, LangChain agents can efficiently execute complex workflows, automate routine tasks, and provide intelligent solutions tailored to user needs. Whether it's querying databases, parsing documents, or interacting with APIs, the strategic use of tools enables LangChain agents to enhance productivity and deliver precise outcomes.\n",
    "\n",
    "\n",
    "Large Language Models (LLMs) inherently lack access to real-time information such as the current date and time, stock market data, and breaking news. This limitation stems from their design, which relies on pre-existing datasets that do not include ongoing updates. Additionally, LLMs are not equipped to perform mathematical calculations directly, which can restrict their utility in scenarios requiring precise numerical operations. To overcome these constraints, LLMs can employ specialized tools. For instance, search engine tools enable LLMs to retrieve the latest information from the web, ensuring up-to-date responses. Similarly, tools designed for mathematical computation can assist LLMs in accurately processing and solving mathematical problems, thereby enhancing their overall capability and accuracy.\n",
    "\n",
    "* [Available Langchain Tools](https://python.langchain.com/v0.2/docs/integrations/tools/)\n",
    "* [Available Langchain Toolkits](https://python.langchain.com/v0.2/docs/integrations/toolkits/)\n",
    "\n",
    "## Tools for Math\n",
    "\n",
    "\n",
    "We will begin our exploration of LangChain tools by using a tool specifically designed for math. Large Language Models (LLMs), like ChatGPT, are generally poor at performing mathematical calculations without the assistance of specialized tools. This is because LLMs are trained primarily on textual data and lack the precision required for accurate arithmetic operations. Consequently, they often produce inaccurate results when asked to perform math independently. To see how a LLM actually performs mathematics, I asked ChatGPT and it gave a decent high-level summary.\n",
    "\n",
    "> Large Language Models (LLMs), like me, do not inherently perform arithmetic calculations the same way a calculator or dedicated algorithm would. Instead, we generate responses based on patterns in the data we've been trained on. Here's a simplified explanation of how we handle such tasks:\n",
    ">\n",
    "> Pattern Recognition: During training, LLMs are exposed to vast amounts of text data, which includes examples of arithmetic and mathematical reasoning. We learn patterns and structures in these examples, enabling us to approximate calculations.\n",
    ">\n",
    "> Token Prediction: When asked to perform a calculation, an LLM doesn't actually \"calculate\" in the traditional sense. Instead, it predicts the most likely sequence of tokens (numbers, in this case) that should follow based on the input. This prediction is influenced by the training data but does not involve real arithmetic operations.\n",
    ">\n",
    "> Approximation and Heuristics: For smaller or simpler calculations, the model might generate the correct answer because it has seen enough examples during training. For larger or more complex calculations, the model might generate an approximate answer or even make a guess based on learned patterns.\n",
    ">\n",
    "> For example, if you ask an LLM to multiply 872947493 by 7492374932, it will try to generate a plausible sequence of digits based on what it has seen in the training data, but this sequence is unlikely to be correct without an actual computational algorithm.\n",
    ">\n",
    ">Here’s a brief comparison of how a traditional method (e.g., a calculator or algorithm) and an LLM approach such a problem:\n",
    ">\n",
    "> * Traditional Method: Uses precise algorithms to perform each step of the multiplication (e.g., long multiplication or fast algorithms like the Karatsuba algorithm).\n",
    "> * LLM Method: Predicts the next sequence of digits based on patterns and probabilities from the training data.\n",
    "So, while an LLM might \"attempt\" to give an answer, it lacks the precision and algorithmic foundation to guarantee accuracy for complex arithmetic without dedicated computational tools.\n",
    "\n",
    "To see this in action, let's ask an LLM to perform a mathematical operation. We will choose numbers that were unlikely in the LLM's training data.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "_5x_qovp1E13",
    "outputId": "064aabbf-fd90-495d-db7d-3e984214bb2c"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "content='8273 times 1821 equals 15,086,213.' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 17, 'total_tokens': 31}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'stop', 'logprobs': None} id='run-3a675778-1103-4384-8350-8082c63cca8e-0' usage_metadata={'input_tokens': 17, 'output_tokens': 14, 'total_tokens': 31}\n"
     ]
    }
   ],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "MODEL = 'gpt-4o-mini'\n",
    "\n",
    "llm = ChatOpenAI(\n",
    "        model=MODEL,\n",
    "        temperature=0.2,\n",
    "        n=1\n",
    "    )\n",
    "\n",
    "print(llm.invoke(\"What is 8273 times 1821?\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "c4ilDY5oB4dT"
   },
   "source": [
    "The resulting number appears reasonable, but that's the point. LLMs are trained to produce believable results, not necessarily correct ones. To verify the LLM, we'll have Python perform this calculation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "r-4t2gN51jom",
    "outputId": "69b84bf5-a6db-4444-c8ab-4ba4811e15b5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "15065133\n"
     ]
    }
   ],
   "source": [
    "print(8273 * 1821)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "lXJis_ZV1qpl",
    "outputId": "4a836215-91d7-45ba-c066-b59aaabe21b3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "9700\n"
     ]
    }
   ],
   "source": [
    "print( abs(15065133 - 15055433 ) )\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2t2lZcP1CVT9"
   },
   "source": [
    "We can see that the LLM was several thousand off. Unfortunately, LangChain does not include a calculator tool, at least as of summer 2024. We will look at two approaches. First, we will use the LangChain-provided PythonREPL. The following code shows how to use PythonREPL."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 53
    },
    "id": "u3O0SkxSwPFk",
    "outputId": "1c4f8341-cd07-446f-d5d9-2d352177138f"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:langchain_experimental.utilities.python:Python REPL can execute arbitrary code. Use with caution.\n"
     ]
    },
    {
     "data": {
      "application/vnd.google.colaboratory.intrinsic+json": {
       "type": "string"
      },
      "text/plain": [
       "'2\\n'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.agents import Tool\n",
    "from langchain_experimental.utilities import PythonREPL\n",
    "\n",
    "python_repl = PythonREPL()\n",
    "\n",
    "python_repl.run(\"print(1+1)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6MO5-3uBFvqO"
   },
   "source": [
    "It begins by importing necessary modules and classes from the LangChain, LangChain OpenAI, and LangChain Community libraries.\n",
    "A tool for executing Python commands (repl_tool) is created using the Tool class, with a description indicating its purpose as a Python shell that requires valid Python commands. The function python_repl.run is assigned to execute the commands.\n",
    "\n",
    "The prompt for the agent is pulled from a hub, specified by the identifier \"hwchase17/openai-functions-agent\".\n",
    "\n",
    "An agent is then created using the create_tool_calling_agent function, which takes the language model (llm), the tools (here, just repl_tool), and the prompt. This agent is wrapped in an AgentExecutor with the verbose mode enabled to provide detailed logs of the execution process.\n",
    "Finally, the agent is invoked with an input command to calculate the product of 8273 and 1821."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "B5UI3eL30iEw",
    "outputId": "2b7d4f21-3932-4f57-956d-ba4e94e4ed68"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/dist-packages/langsmith/client.py:312: LangSmithMissingAPIKeyWarning: API key must be provided when using hosted LangSmith API\n",
      "  warnings.warn(\n",
      "/usr/local/lib/python3.10/dist-packages/langsmith/client.py:5515: LangChainBetaWarning: The function `loads` is in beta. It is actively being worked on, so the API may change.\n",
      "  prompt = loads(json.dumps(prompt_object.manifest))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `python_repl` with `{'input': '8273 * 1821'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m15065133\u001b[0m\u001b[32;1m\u001b[1;3mThe result of \\( 8273 \\times 1821 \\) is \\( 15,065,133 \\).\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n",
      "{'input': 'What is 8273 * 1821?', 'output': 'The result of \\\\( 8273 \\\\times 1821 \\\\) is \\\\( 15,065,133 \\\\).'}\n"
     ]
    }
   ],
   "source": [
    "from langchain import hub\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.agents import create_tool_calling_agent\n",
    "from langchain.agents import AgentExecutor\n",
    "from langchain.tools import StructuredTool\n",
    "from pydantic import BaseModel\n",
    "\n",
    "MODEL = 'gpt-4o-mini'\n",
    "\n",
    "llm = ChatOpenAI(\n",
    "    model=MODEL,\n",
    "    temperature=0.2,\n",
    "    n=1\n",
    ")\n",
    "\n",
    "# Define a Pydantic schema for the input arguments\n",
    "class PythonReplInput(BaseModel):\n",
    "    input: str\n",
    "\n",
    "# Define the tool using StructuredTool with an args_schema\n",
    "def run_python_code(input: str, **kwargs):\n",
    "    try:\n",
    "        # Safely evaluate the input string as a Python expression\n",
    "        result = eval(input)\n",
    "        return str(result)\n",
    "    except Exception as e:\n",
    "        return str(e)\n",
    "\n",
    "repl_tool = StructuredTool(\n",
    "    name=\"python_repl\",\n",
    "    description=\"A Python shell. Use this to execute python commands. Input should be a valid python command. If you want to see the output of a value, you should print it out with `print(...)`.\",\n",
    "    func=run_python_code,  # Use the custom Python REPL function\n",
    "    args_schema=PythonReplInput  # Define the expected input schema\n",
    ")\n",
    "\n",
    "prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
    "\n",
    "tools = [repl_tool]\n",
    "agent = create_tool_calling_agent(llm, tools, prompt)\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n",
    "\n",
    "# Pass a string Python command as input\n",
    "result = agent_executor.invoke({\"input\": \"What is 8273 * 1821?\"})\n",
    "\n",
    "print(result)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "7-iL2vLrGYDL"
   },
   "source": [
    "## Create a Cusom Math Tool\n",
    "\n",
    "The Python REPL tool we just used can execute any Python command. Therefore, it can be a security concern if we only wish to perform math calculations. In this section, we will see how to create a custom tool that can only perform basic math calculations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "J0Fux9SZGfu0",
    "outputId": "69e761a2-3fc8-4917-b8df-c3046a9e3f7d"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Result: 3.0\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<ipython-input-7-34b3ab86af62>:38: LangChainDeprecationWarning: The method `Chain.__call__` was deprecated in langchain 0.1.0 and will be removed in 1.0. Use invoke instead.\n",
      "  result = chain(inputs)\n"
     ]
    }
   ],
   "source": [
    "from langchain_openai import OpenAI\n",
    "import numpy as np\n",
    "from langchain.chains.base import Chain\n",
    "\n",
    "class SafeCalculator:\n",
    "    def calculate(self, expression):\n",
    "        try:\n",
    "            # Evaluate the mathematical expression using NumPy\n",
    "            result = eval(expression, {\"__builtins__\": None}, {\"np\": np})\n",
    "            return result\n",
    "        except Exception as e:\n",
    "            return str(e)\n",
    "\n",
    "class CalculatorChain(Chain):\n",
    "    calculator: SafeCalculator\n",
    "\n",
    "    def _call(self, inputs):\n",
    "        expression = inputs[\"expression\"]\n",
    "        result = self.calculator.calculate(expression)\n",
    "        return {\"result\": result}\n",
    "\n",
    "    @property\n",
    "    def input_keys(self):\n",
    "        return [\"expression\"]\n",
    "\n",
    "    @property\n",
    "    def output_keys(self):\n",
    "        return [\"result\"]\n",
    "\n",
    "# Initialize the safe calculator tool\n",
    "safe_calculator = SafeCalculator()\n",
    "\n",
    "# Example usage\n",
    "chain = CalculatorChain(calculator=safe_calculator)\n",
    "expression = \"3 * (2 + 5) / 7\"\n",
    "inputs = {\"expression\": expression}\n",
    "result = chain(inputs)\n",
    "print(f\"Result: {result['result']}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "VN_jww59Ick9",
    "outputId": "f1a6aae6-5e50-4cdd-a22e-c61231f43484"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/dist-packages/langsmith/client.py:312: LangSmithMissingAPIKeyWarning: API key must be provided when using hosted LangSmith API\n",
      "  warnings.warn(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `safe_calc` with `{'input': '8273 * 1821'}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3m15065133\u001b[0m\u001b[32;1m\u001b[1;3mThe result of \\( 8273 \\times 1821 \\) is \\( 15,065,133 \\).\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n",
      "{'input': '8273 * 1821', 'output': 'The result of \\\\( 8273 \\\\times 1821 \\\\) is \\\\( 15,065,133 \\\\).'}\n"
     ]
    }
   ],
   "source": [
    "from langchain import hub\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.agents import create_tool_calling_agent\n",
    "from langchain.agents import AgentExecutor\n",
    "from langchain.tools import StructuredTool\n",
    "from pydantic import BaseModel\n",
    "\n",
    "MODEL = 'gpt-4o-mini'\n",
    "\n",
    "llm = ChatOpenAI(\n",
    "    model=MODEL,\n",
    "    temperature=0.2,\n",
    "    n=1\n",
    ")\n",
    "\n",
    "# Define a Pydantic schema for the input arguments\n",
    "class MathExpressionInput(BaseModel):\n",
    "    input: str\n",
    "\n",
    "# Define a safe calculator function\n",
    "def safe_calculator(input: str, **kwargs):\n",
    "    try:\n",
    "        # Safely evaluate the input math expression\n",
    "        result = eval(input)\n",
    "        return str(result)\n",
    "    except Exception as e:\n",
    "        return str(e)\n",
    "\n",
    "# Define the tool using StructuredTool with args_schema\n",
    "safe_math_tool = StructuredTool(\n",
    "    name=\"safe_calc\",\n",
    "    description=\"A math calculator used to evaluate mathematical expressions. Input should be a valid math expression, similar to Python.\",\n",
    "    func=safe_calculator,  # Use the safe calculator function\n",
    "    args_schema=MathExpressionInput  # Define the expected input schema\n",
    ")\n",
    "\n",
    "prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
    "\n",
    "tools = [safe_math_tool]\n",
    "agent = create_tool_calling_agent(llm, tools, prompt)\n",
    "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n",
    "\n",
    "# Pass a mathematical expression as input\n",
    "result = agent_executor.invoke({\"input\": \"8273 * 1821\"})\n",
    "\n",
    "print(result)\n"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3.11 (genai)",
   "language": "python",
   "name": "genai"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
